Validation

Validation framework

Validation overview

DataObjects.Net includes consistency validation framework allowing to validate single property values, entire entities and entity graphs.

Each transaction contains its own ValidationContext – class responsible for validation of all entities changed in this transaction. Entity class implements IValidaionAware interface allowing it to be validated by validation context. When some entity is changed, it calls Validate method to inform the appropriate validation context. Validation context can be in one of following states:

  • Consistent – context immediately validates the entity by calling its OnValidate() method. Entity descendants should override OnValidate() method to validate its state (property values) and throw an exception, if state is invalid.
  • Inconsistent – context adds the entity to internal registry to validate it on entering consistent state.

If validation is failed, validation infrastructure will throw AggregateException with list of validation errors found in validated entities:

try {
  using (var transactionScope = session.OpenTransaction()) {
    using (var inconsistencyRegion = session.DisableValidation()) {

      // Change your entities here

      inconsistencyRegion.Complete();
    }
    transactionScope.Complete();
  }
}
catch(AggregateException exception) {
  Console.WriteLine("Following validation errors were found:");
  foreach (var error in exception.GetFlatExceptions())
    Console.WriteLine(error.Message);
}

You can also enforce validation of all changed entities inside inconsistency region by calling Session.Validate() method.

Validation on demand

Continuous validation can be useful in some cases, but often it is enough to validate all changed entities when transaction is being committed. In such cases you can switch validation mode to OnDemand instead of Continuous. To do this change ValidationMode property of DomainConfiguration instance before domain is built:

domainConfiguration.ValidationMode = ValidationMode.OnDemand;

When this mode is switched on, single inconsistency region is opened for each transaction, so validation context will always be in inconsistent state and entities will be validated on transaction commit only. But you still can validate changed entities with Session.Validate() method any time you want.

Object level validation rules

To define validation rules you should implement object-level validation logic in OnValidate() method that will be called on entity validation. This method should check entity state and throw an exception if it is invalid.

[HierarchyRoot]
public class Person : Entity
{
  // ...

  [Field]
  public bool IsSubscribedOnNews { get; set;}

  [Field]
  public string Email { get; set;}

  protected override void OnValidate()
  {
    base.OnValidate();

    if (IsSubscribedOnNews && string.IsNullOrEmpty(Email))
      throw new Exception("Can't subscribe on news (email is not specified).");
  }
}

Property constraints

Overview

Another way to define validation rules is to mark entity properties by special attributes – property constraints. Property constraints are special property-level validation aspects that can be applied to any property by marking it with appropriate attribute. Each constraint implements some simple validation rule for property value. For example NotNullConstraint ensures that property value is not null, RegexConstraint ensures that string value matches specified regular expression pattern.

[LengthConstraint(Min = 2, Max = 128)]
[NotNullOrEmptyConstraint]
public string FirstName { get; set;}

[PastConstraint]
public DateTime BirthDay { get; set; }

[EmailConstraint]
public string Email { get; set;}

Each property constraint attribute has two additional properties: Message and Mode.

Message property specifies message for exception that should be thrown if property value is invalid. Message can contain some special variables, that will be replaced automatically: Property value – {value}, property name – {PropertyName} and constraint parameters – {[parameter name]}.

[PastConstraint(Message = "Birth day must be in the past.")]
public DateTime BirthDay { get; set; }

[RangeConstraint(
  Min = 0.8,
  Max = 2.13,
  Message = "Incorrect '{PropertyName}' value: {value}, " +
    "it can not be less than {Min} and greater than {Max}.")]
public double Height { get; set;}

If we set height property to 2.5, we’ll get exception with such message: “Incorrect ‘Height’ value: 2.5, it can not be less than 0.8 and greater than 2.13.”

By default property constraints are used when entity is being validated, but it’s also possible to throw exception on setting new value to the property. PropertyConstraintAspect has special property Mode of ConstrainMode type. It has two available values: OnValidate (default value) and OnSetValue.

[PastConstraint(Mode = ConstrainMode.OnSetValue)]
public DateTime BirthDay { get; set; }

Pre-defined validation aspects

DataObjects.Net validation framework includes following predefined constraints:

  • EmailConstraint – Ensures that email address is in correct format
  • FutureConstraint – Ensures that date value is in the future
  • LengthConstraint(Min, Max) – Ensures field length (or item count) fits in specified range
  • NotEmptyConstraint – Ensures that property value is not empty string
  • NotNullConstraint – Ensures property value is not null
  • NotNullOrEmptyConstraint – Ensures that property value is not null or empty string
  • PastConstraint – Ensures that date value is in the past
  • RangeConstraint(Min, Max) – Ensures field value fits in the specified range
  • RegexConstraint(Pattern) – Ensures property value matches specified regular expression

Implementing custom constraints

It is possible to implement your own property constraints inheriting it from abstract class PropertyConstraintAspect.

[Serializable]
public class PhoneNumberConstraint : PropertyConstraintAspect
{
  private const string PhoneNumberPattern = "^[2-9]\\d{2}-\\d{3}-\\d{4}$";

  [NonSerialized]
  private Regex phoneNumberRegex;

  public override bool IsSupported(Type valueType)
  {
    return valueType==typeof(string);
  }

  public override bool CheckValue(object value)
  {
    string phoneNumber = (string) value;
    return
      string.IsNullOrEmpty(phoneNumber) ||
      phoneNumberRegex.IsMatch(phoneNumber);
  }

  protected override string GetDefaultMessage()
  {
    return "Phone number is incorrect";
  }

  protected override void Initialize()
  {
    base.Initialize();
    phoneNumberRegex = new Regex(PhoneNumberPattern, RegexOptions.Compiled);
  }
}

We create regular expression instance in Initialize() method instead of constructor, we do this since constraints is PostSharp aspects and it is created in compile time also, but we need regex to be created in runtime only.

[PhoneNumberConstraint]
public string Phone { get; set;}